#include "stdafx.h"
#include "XFileReaderImpl.h"

#include "XFileDef.h"
#include "FileUtil.h"
#include "Util.h"
#include "XStrUtil.h"
#include "XBSTR.h"

#include "IXAutoBuffer.h"
#include "IXFileSystem.h"
#include "IntSkinImpl.h"

IMPLEMENT_SKIN_CLASS(CXFileReaderImpl);

CXFileReaderImpl::CXFileReaderImpl()
{
    m_hXFile = INVALID_HANDLE_VALUE;
    m_hFileMapping = NULL;
    m_dwXFileSize = 0;
    m_bRealFS = FALSE;
}

CXFileReaderImpl::~CXFileReaderImpl()
{
    CloseXFile();
}

// IXFileWriter
HRESULT CXFileReaderImpl::put_XNC(IN LPCTSTR szXNC)
{
    m_bRealFS = !Util::IsXPath(szXNC);

#if _DEBUG_XNC_USE_REALFS_
    m_bRealFS = TRUE;
#endif //

    XPtr<IXFileSystem> pFileSystem = NULL;
    HRESULT hResult = Util::CreateObject(&pFileSystem);
    if(FAILED(hResult))
        return hResult;

    XBSTR bstrRootPath, bstrRelativePath;
    hResult = pFileSystem->ParserXNC(szXNC, &bstrRootPath, &bstrRelativePath);
    if(FAILED(hResult))
        return hResult;

    m_strRootPath = bstrRootPath;
    m_strRootPath = XStrUtil::ToLower(m_strRootPath);
    m_strRelativePath = bstrRelativePath;
    m_strRelativePath = XStrUtil::ToLower(m_strRelativePath);

    if(m_bRealFS)
        hResult = put_RealFS(m_strRootPath.GetData(), m_strRelativePath.GetData());
    else
        hResult = put_XFilePath(m_strRootPath.GetData(), m_strRelativePath.GetData());
    return hResult;
}

HRESULT CXFileReaderImpl::LoadObject(IN LPCTSTR szObjectName, OUT IXFileMapBuffer** pBuffer)
{
    if(*pBuffer != NULL)
    {
        (*pBuffer)->Release();
        (*pBuffer) = NULL;
    }

    if(!IsValid())
    {
        return E_FAIL;
    }

    XString strObjectName = XStrUtil::ToLower(szObjectName);
//     if(m_bRealFS)
//         strObjectName = FileUtil::ExtractFileName(strObjectName.GetData());

    XObjectDataMap::iterator IteObject = m_XObjectDataMap.find(strObjectName);
    if(IteObject == m_XObjectDataMap.end())
    {
        return E_INVALIDARG;
    }
    else
    {
        HRESULT hResult = E_INVALIDARG;
        const XString & refName = IteObject->first;
        const stXObjectData& data = IteObject->second;

        void* pData = NULL;
        do 
        {

            DWORD dwOffset = 0;
            DWORD dwLength = 0;

            dwOffset = (DWORD)Util::GetPrevAlignedAddress((LPVOID)data.dwFileOffset);
            dwLength = (data.dwFileOffset + data.dwFileLength) - dwOffset;
            if(m_bRealFS)
            {
                XString strFilePath = FileUtil::CombinePath(m_strRootPath.GetData(), IteObject->first.GetData());
                HANDLE hFile = FileUtil::OpenFileForRead(strFilePath);
                if(hFile != INVALID_HANDLE_VALUE)
                {
                    HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwLength, NULL);
                    if(hFileMapping != NULL)
                    {
                        pData = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, dwOffset, dwLength);
                        CloseHandle(hFileMapping);
                    }
                    CloseHandle(hFile);
                }
            }
            else
            {
                pData = MapViewOfFile(m_hFileMapping, FILE_MAP_READ, 0, dwOffset, dwLength);
            }
            if(pData == NULL)
                break;

            if(FAILED(Util::CreateObject(pBuffer)))
                break;

            if(FAILED((*pBuffer)->put_Data(pData, data.dwFileOffset - dwOffset, data.dwFileLength)))
                break;

            hResult = S_OK;
        } while (FALSE);

        if(FAILED(hResult))
        {
            if(*pBuffer != NULL)
            {
                (*pBuffer)->Release();
                (*pBuffer) = NULL;
            }

            if(pData != NULL)
                UnmapViewOfFile(pData);

            return hResult;
        }

        return hResult;
    }
}

HRESULT CXFileReaderImpl::EnumObject(IN OUT DWORD pos, OUT BSTR* bstrObjectName)
{
    if(!IsValid())
    {
        return E_FAIL;
    }

    if(pos >= m_XObjectDataMap.size())
        return E_INVALIDARG;

    XObjectDataMap::iterator IteObj = m_XObjectDataMap.begin();
    for(DWORD i=0; i<pos; ++ i)
    {
        ++ IteObj;
    }

    const XString & refName = IteObj->first;
    const stXObjectData& data = IteObj->second;

    return ::SysReAllocString(bstrObjectName, refName.GetData()) ? S_OK : E_FAIL;
}

DWORD CXFileReaderImpl::GetObjectCount()
{
    if(!IsValid())
    {
        return 0;
    }

    return m_XObjectDataMap.size();
}

BOOL CXFileReaderImpl::IsFileExists(IN LPCTSTR szFilePath)
{
    return IsObjectExists(szFilePath, FALSE);
}

BOOL CXFileReaderImpl::IsFolderExists(IN LPCTSTR szFolderPath)
{
    return IsObjectExists(szFolderPath, TRUE);
}

// IXBaseObj
void CXFileReaderImpl::OnFinalRelease()
{
    __super::OnFinalRelease();
    CloseXFile();
}

// CXFileReaderImpl
void CXFileReaderImpl::CloseXFile()
{
    if(m_hFileMapping != NULL)
    {
        CloseHandle(m_hFileMapping);
        m_hFileMapping = NULL;
    }

    if(m_hXFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hXFile);
        m_hXFile = INVALID_HANDLE_VALUE;
        m_dwXFileSize = 0;
    }
    m_XObjectDataMap.clear();
}

BOOL CXFileReaderImpl::ParseXFile(IN LPCTSTR szRelativePath)
{
    m_XObjectDataMap.clear();

    if(m_hXFile == INVALID_HANDLE_VALUE)
        return FALSE;

    IXAutoBuffer* pAutoBuffer = NULL;
    if(FAILED(Util::CreateObject(&pAutoBuffer)))
        return FALSE;

    BOOL bResult = FALSE;
    DWORD dwOffset = 0;
    DWORD dwTemp = 0;
    XString strObjectName;
    stXObjectData data;

    do 
    {
        dwOffset = sizeof(stXFileHeader);

        bResult = TRUE;
        while(dwOffset < m_dwXFileSize && bResult)
        {
            // SeekFile
            bResult = FileUtil::SeekFile(m_hXFile, dwOffset, FILE_BEGIN);
            if(!bResult)
                break;

            // Read XFileIndex Size
            bResult = FileUtil::ReadData(m_hXFile, &dwTemp);
            if(!bResult)
                break;

            if(dwTemp < sizeof(DWORD))
            {
                bResult = FALSE;
                break;
            }

            stXFileIndex* pIndex = NULL;
            if(FAILED(pAutoBuffer->GetBuffer((void**)&pIndex, dwTemp)) || pIndex == NULL)
            {
                bResult = FALSE;
                break;
            }

            pIndex->dwSize = dwTemp;
            bResult = FileUtil::ReadData(m_hXFile, ((BYTE*)pIndex) + offsetof(stXFileIndex, dwFileLength), dwTemp - sizeof(DWORD), dwTemp);
            if(!bResult || pIndex->dwSize != dwTemp + sizeof(DWORD))
                break;

            strObjectName = XStrUtil::ToLower(pIndex->szFilePath);
            if(szRelativePath[0] == 0 || strObjectName.find(szRelativePath) != XString::npos)
            {
                data.dwFileLength = pIndex->dwFileLength;
                data.dwFileOffset = dwOffset + pIndex->dwSize;
                m_XObjectDataMap.insert(XObjectDataMap::value_type(strObjectName, data));
            }

            dwOffset += pIndex->dwSize + pIndex->dwFileLength;
        }

        bResult = bResult && (dwOffset >= m_dwXFileSize);
    } while (FALSE);

    pAutoBuffer->Release();

    return bResult;
}

BOOL CXFileReaderImpl::IsValid()
{
    return m_bRealFS || (m_hXFile != INVALID_HANDLE_VALUE && m_hFileMapping != NULL);
}

//////////////////////////////////////////////////////////////////////////
// CXFileReaderImpl

HRESULT CXFileReaderImpl::put_XFilePath(IN LPCTSTR szXFilePath, LPCTSTR szRelative)
{
    CloseXFile();

    XString strRelative(szRelative);
    strRelative = XStrUtil::ToLower(strRelative);

    HRESULT hResult = E_FAIL;
    do 
    {
        m_hXFile = FileUtil::OpenFileForRead(szXFilePath);
        if(m_hXFile == INVALID_HANDLE_VALUE)
        {
            hResult = E_ACCESSDENIED;
            break;
        }

        if(!FileUtil::SeekFile(m_hXFile, 0, FILE_BEGIN))
        {
            hResult = E_ACCESSDENIED;
            break;
        }

        stXFileHeader header = {0};
        if(!FileUtil::ReadData(m_hXFile, &header))
        {
            hResult = E_ACCESSDENIED;
            break;
        }

        if(header.dwTag != XFILE_HEADER_TAG || header.dwVersion != XFILE_VERSION)
        {
            hResult = E_INVALIDARG;
            break;
        }

        m_dwXFileSize = FileUtil::GetFileSize(m_hXFile);

        if(!ParseXFile(szRelative))
        {
            hResult = E_INVALIDARG;
            break;
        }

        m_hFileMapping = CreateFileMapping(m_hXFile, NULL, PAGE_READONLY, 0, m_dwXFileSize, NULL);
        if(m_hFileMapping == NULL)
        {
            hResult = E_ACCESSDENIED;
            break;
        }

        hResult = S_OK;

    } while (FALSE);

    if(FAILED(hResult) && m_hXFile != INVALID_HANDLE_VALUE)
    {
        CloseXFile();
    }

    return hResult;
}

HRESULT CXFileReaderImpl::put_RealFS(IN LPCTSTR szXFilePath, LPCTSTR szRelative)
{
    CloseXFile();

    XString strPath = FileUtil::CombinePath(szXFilePath, szRelative);
    if(ParseRealFS(strPath.GetData()))
        return S_OK;
    else
        return E_FAIL;
}

BOOL CXFileReaderImpl::ParseRealFS(LPCTSTR szPath)
{
    XString strTemp, strFilter;
    strFilter = FileUtil::CombinePath(szPath, _T("*"));

    WIN32_FIND_DATA data = {0};
    HANDLE hFindFile = FindFirstFile(strFilter.GetData(), &data);
    if(hFindFile == INVALID_HANDLE_VALUE)
        return FALSE;

    BOOL bResult = TRUE;
    stXObjectData objData = {0};
    do 
    {
        if(IntSkinImpl::CheckFlags(data.dwFileAttributes, FILE_ATTRIBUTE_SYSTEM)
            || IntSkinImpl::CheckFlags(data.dwFileAttributes, FILE_ATTRIBUTE_HIDDEN)
            || IntSkinImpl::CheckFlags(data.dwFileAttributes, FILE_ATTRIBUTE_TEMPORARY)
            || _tcscmp(data.cFileName, _T(".")) == 0
            || _tcscmp(data.cFileName, _T("..")) == 0)
        {
            continue;
        }

        if(IntSkinImpl::CheckFlags(data.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
        {
            // if(_tcsicmp(data.cFileName, _XSTRING_BUNDLE_NODE_NAME_) != 0)
            {
                strTemp = FileUtil::CombinePath(szPath, data.cFileName);
                bResult = ParseRealFS(strTemp.c_str());
                if(!bResult)
                    break;
            }
        }
        else
        {
            strTemp = FileUtil::CombinePath(szPath, data.cFileName);
            strTemp = XStrUtil::ToLower(strTemp);
            if(strTemp.find(m_strRootPath.GetData()) == 0)
                strTemp = strTemp.substr(m_strRootPath.Length() + 1);
            objData.dwFileLength = data.nFileSizeLow;
            m_XObjectDataMap.insert(XObjectDataMap::value_type(strTemp, objData));
        }

    } while (FindNextFile(hFindFile, &data));
    FindClose(hFindFile);

    return bResult;
}

BOOL CXFileReaderImpl::IsObjectExists(IN LPCTSTR szObjPath, IN BOOL bFolder)
{
    if(szObjPath == NULL || szObjPath[0] == 0)
        return FALSE;

    XString strObjectName = XStrUtil::ToLower(szObjPath);
    size_t pos = strObjectName.find(_T(':'));
    if(pos != XString::npos && strObjectName.size() > 0)
    {
        strObjectName = strObjectName.substr(pos + 1);
    }

    strObjectName.insert(strObjectName.begin(), m_strRelativePath.c_str());
    if(bFolder && strObjectName.empty())
        return TRUE;    // Root Directory

    if(bFolder && strObjectName[strObjectName.Length() - 1] != _T('\\'))
        strObjectName += _T('\\');

    XObjectDataMap::iterator IteObject = m_XObjectDataMap.begin();
    for(; IteObject != m_XObjectDataMap.end(); ++ IteObject)
    {
        XString strPath = IteObject->first;
        // if(_tcsncmp(strObjectName.c_str(), IteObject->first.c_str(), strObjectName.Length()) == 0)
        if(_tcsncmp(strObjectName.c_str(), strPath.c_str(), strObjectName.Length()) == 0)
            return TRUE;
    }
    return FALSE;
}
